package team02.server.core.parser;

import static team02.client.core.test.Footer.STATISTIC_TYPE_ESTIMATE;
import static team02.client.core.test.Footer.STATISTIC_TYPE_ESTIMATE_PERCENT;
import static team02.client.core.test.Footer.STATISTIC_TYPE_ESTIMATE_VALUE;
import static team02.client.core.test.Footer.TYPE_LIST;
import static team02.client.core.test.Header.END_DATE_TIME;
import static team02.client.core.test.Header.EXAMINEE;
import static team02.client.core.test.Header.EXAMINEE_ID;
import static team02.client.core.test.Header.EXAMINEE_NAME;
import static team02.client.core.test.Header.EXAMINEE_PASSWORD;
import static team02.client.core.test.Header.START_DATE_TIME;
import static team02.client.core.test.Header.SUBTITLE;
import static team02.client.core.test.Header.TEST_DURATION;
import static team02.client.core.test.Header.TEST_TYPE;
import static team02.client.core.test.Header.TITLE;
import static team02.client.core.test.question.Question.QUESTION_ANSWER_TYPE_KS;
import static team02.client.core.test.question.Question.QUESTION_ANSWER_TYPE_KSA;
import static team02.client.core.test.question.Question.QUESTION_ANSWER_TYPE_KSO;
import static team02.client.core.test.question.Question.QUESTION_ANSWER_TYPE_KW;
import static team02.client.core.test.question.Question.QUESTION_ANSWER_TYPE_KWA;
import static team02.client.core.test.question.Question.QUESTION_ANSWER_TYPE_KWAA;
import static team02.client.core.test.question.Question.QUESTION_ANSWER_TYPE_KWAO;
import static team02.client.core.test.question.Question.QUESTION_ANSWER_TYPE_KWP;
import static team02.client.core.test.question.Question.QUESTION_ANSWER_TYPE_KWPA;
import static team02.client.core.test.question.Question.QUESTION_ANSWER_TYPE_KWPO;
import static team02.client.core.test.question.Question.QUESTION_EXAMINER_INSERT;
import static team02.client.core.test.question.Question.QUESTION_EXAMINER_INSERT_TYPE_CHECK;
import static team02.client.core.test.question.Question.QUESTION_EXAMINER_INSERT_TYPE_COMMENT;
import static team02.client.core.test.question.Question.QUESTION_EXAMINER_INSERT_TYPE_MARK;
import static team02.client.core.test.question.Question.QUESTION_FIGURE;
import static team02.client.core.test.question.Question.QUESTION_LIMIT;
import static team02.client.core.test.question.Question.QUESTION_LIMIT_TYPE_TRAP;
import static team02.client.core.test.question.Question.QUESTION_TIME_OUT;
import static team02.client.core.test.question.Question.QUESTION_TYPE_ANSWER_ANC;
import static team02.client.core.test.question.Question.TYPE_SELECTION;
import static team02.client.core.test.question.Question.TYPE_WRITE;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;

import team02.client.core.config.Config;
import team02.client.core.config.ParserState;
import team02.client.core.config.SystemConfig;
import team02.client.core.test.Examinee;
import team02.client.core.test.Footer;
import team02.client.core.test.Header;
import team02.client.core.test.Test;
import team02.client.core.test.question.Question;
import team02.client.core.test.question.QuestionAN;
import team02.client.core.test.question.QuestionS;
import team02.client.core.test.question.QuestionWR;
import team02.client.util.Pair;
import team02.server.exception.ParserException;

public class CSVParser {

	public static final int YEAR = 1;
	public static final int MONTH = 2;
	public static final int DAY = 3;
	public static final int HOUR = 4;
	public static final int MINUTE = 5;
	public static final int SECOND = 6;

	//	private static final Pattern REMOVE_COMMENT_MULTIPLE_LINE_1 = Pattern.compile(System.getProperty("line.separator")
	//			+ "/\\*.*?\\*/" + System.getProperty("line.separator"), Pattern.DOTALL);
	private static final Pattern REMOVE_COMMENT_MULTIPLE_LINE_2 = Pattern.compile("/\\*.*?\\*/", Pattern.DOTALL);
	//	private static final Pattern REMOVE_COMMENT_SINGLE_LINE_1 = Pattern.compile(System.getProperty("line.separator")
	//			+ "#.*" + System.getProperty("line.separator"));
	private static final Pattern REMOVE_COMMENT_SINGLE_LINE_2 = Pattern.compile("#.*");

	/* QUESTION CONSTANTS */
	public static final Pattern QUESTION = Pattern.compile("Q\\((\\d+)\\)");
	public static final Pattern QUESTION_SELECTION = Pattern.compile("S\\((\\d+)\\)");
	public static final Pattern QUESTION_WRITE = Pattern.compile("WR\\((\\d+)\\)");
	public static final Pattern QUESTION_SCORE = Pattern.compile("SC\\((\\d+)\\)");
	public static final Pattern QUESTION_ANSWER = Pattern.compile("AN\\((\\d+)\\)");
	
	private static final String DEFAULT_ENCODING = "utf-16";
	public static ParserState parserState = new ParserState();

	public static final int PARSE_HEADER = 1;
	public static final int PARSE_QUESTION = 2;
	public static final int PARSE_FOOTER = 3;

	public static Test parseCSV(InputStream inputStream) throws IOException, ParserException {

		Test test = new Test();

		String content = IOUtils.toString(inputStream, DEFAULT_ENCODING) + "\n";
		content = removeComment(content);

		String[] lines = content.split(System.getProperty("line.separator"));

		Header h = new Header();
		Footer f = new Footer();

		int parsing = PARSE_HEADER;
		//		int currentQuestion = 0;
		try {
		for (int i = 0; i < lines.length; i++) {
			System.out.println("===" + lines[i]);
			lines[i] = lines[i].trim();
			CSVParser.parserState.setLineNumber(i + 1);
			if (StringUtils.isEmpty(lines[i])) continue;//skip blank line
			if (parsing == PARSE_HEADER) {
				try {
					parseHeader(h, lines[i]);
				} catch (ParserException e) {
					Matcher matcher = QUESTION.matcher(parserState.getToken());
					if (matcher.matches()) {
						test.setHeader(h);//finish header
						if(test.getEndTime() - test.getStartTime() > 0x7fffffff) {//khoang 24 ngay
							throw new ParserException(ParserException.SEMANTIC_ERROR,
									"Test time's tooooooooooooooooo loooooooooooooooooooooooooong");
						}
						if (test.getTitle() == null || test.getStartTime() == 0 || test.getEndTime() == 0
								|| test.getExamineeList() == null || test.getExamineeList().size() == 0
								|| test.getType() == null) { throw new ParserException(ParserException.SEMANTIC_ERROR,
								"Header エラー。"); }
						parsing = PARSE_QUESTION;
						//						currentQuestion = Integer.parseInt(matcher.group(1));
					} else throw e;
				}
			}
			if (parsing == PARSE_QUESTION) {
				try {
					parseQuestion(test, lines[i]);
				} catch (ParserException e) {
					if (TYPE_LIST.contains(parserState.getToken())) {
						parsing = PARSE_FOOTER;
					} else throw e;
				}
			}
			if (parsing == PARSE_FOOTER) {
				parseFooter(f, lines[i]);
				test.setFooter(f);
				
			}
		}
		}catch (ArrayIndexOutOfBoundsException e) {
			throw new ParserException(ParserException.SYNTAX_ERROR, "[文法のエラー] 行 " + parserState.getLineNumber()+" は情報が不足！");
		}
		if (test.getStatisticTypeList() == null || test.getStatisticTypeList().size() == 0) throw new ParserException(ParserException.SYNTAX_ERROR,
				"Footer は無い。");
		return test;
	}

	private static void parseFooter(Footer f, String line) throws ParserException {
		String[] tokens = line.split(",");
		CSVParser.parserState.setToken(tokens[0]);
		if (TYPE_LIST.contains(tokens[0])) {
			if (STATISTIC_TYPE_ESTIMATE.equals(tokens[0])) {
				if (STATISTIC_TYPE_ESTIMATE_PERCENT.equals(tokens[1])
						|| STATISTIC_TYPE_ESTIMATE_VALUE.equals(tokens[1])) {
					f.addStatisticType(tokens[0]);
					f.setEstimateType(tokens[1]);
				} else {
					CSVParser.parserState.setToken(tokens[1]);
					throw new ParserException(ParserException.SYNTAX_ERROR, SystemConfig.getConfigLanguage()
							.getParserMessage(Config.SYNTAX_ERROR));
				}
			} else {
				f.addStatisticType(tokens[0]);
			}
		} else {
			throw new ParserException(ParserException.SYNTAX_ERROR, SystemConfig.getConfigLanguage().getParserMessage(
					Config.SYNTAX_ERROR));
		}
	}

	/**
	 * @param t
	 * @param line
	 * @throws ParserException
	 */
	private static void parseQuestion(Test t, String line) throws ParserException {
		String[] tokens = line.split(",");
		for (int i = 0; i < tokens.length; i++) {
			tokens[i] = tokens[i].trim();
		}
		String keyword = tokens[0];
		parserState.setToken(keyword);

		Matcher matcher;
		if (!(matcher = QUESTION.matcher(keyword)).matches()) { throw new ParserException(ParserException.SYNTAX_ERROR,
				SystemConfig.getConfigLanguage().getParserMessage(Config.SYNTAX_ERROR)); }
		int questionNo = Integer.parseInt(matcher.group(1));
		int currentSize = t.size();
		if (currentSize < questionNo) {
			for (int i = currentSize; i < questionNo; i++) {
				t.addQuestion(new Question());
			}
		}
		Question q = t.get(questionNo - 1);
		String token1 = tokens[1];
		q.setQuestionNo(questionNo);

		if (TYPE_SELECTION.equals(token1)) {
			q.setType(token1);
			q.setContent(tokens[2]);
		} else if (TYPE_WRITE.equals(token1)) {
			q.setType(token1);
			q.setContent(tokens[2]);
		} else if (QUESTION_FIGURE.equals(token1)) {
			q.setHasFigure(true);
			q.setFigure(tokens[2]);
		} else if (QUESTION_LIMIT.equals(token1)) {

			q.setTimeStyle(QUESTION_LIMIT);
			q.setLimitStyle(QUESTION_LIMIT_TYPE_TRAP);
			q.setLimitPoints(new Pair(getTime(tokens[3], tokens[4]), getTime(tokens[3], tokens[4]) + getTime(tokens[5], tokens[6])));
			
		} else if (QUESTION_TIME_OUT.equals(token1)) {

			q.setTimeStyle(QUESTION_TIME_OUT);
			q.setTimeOutDuration(getTime(tokens[2], tokens[3]));

		} else if (QUESTION_EXAMINER_INSERT.equals(token1)) {
			q.setHasExaminerInsert(true);
			if (QUESTION_EXAMINER_INSERT_TYPE_COMMENT.equals(tokens[2])
					|| QUESTION_EXAMINER_INSERT_TYPE_CHECK.equals(tokens[2])) {

				q.setExaminerInsertType(tokens[2]);
			} else {
				CSVParser.parserState.setToken(tokens[2]);
				throw new ParserException(ParserException.SYNTAX_ERROR, SystemConfig.getConfigLanguage()
						.getParserMessage(Config.SYNTAX_ERROR));
			}
		} else if (QUESTION_TYPE_ANSWER_ANC.equals(token1)) {
			for (int i = 3; i < tokens.length; i++) {
				if (tokens[i].isEmpty()) continue;
				matcher = QUESTION_ANSWER.matcher(tokens[i]);
				if (!matcher.matches()) {
					CSVParser.parserState.setToken(tokens[i]);
					throw new ParserException(ParserException.SYNTAX_ERROR, SystemConfig.getConfigLanguage()
							.getParserMessage(Config.SYNTAX_ERROR));
				}
				QuestionAN answer = q.getAnswer(Integer.parseInt(matcher.group(1)));
				if (answer == null) { throw new ParserException(ParserException.SEMANTIC_ERROR, "[意味のエラー]　解答'"
						+ tokens[i] + "'　がありません、 行 " + parserState.getLineNumber()); }
				q.getQuestionANC().add(answer);
			}
		} else if ((matcher = QUESTION_SELECTION.matcher(token1)).matches()) {
			int selectionNumber = Integer.parseInt(matcher.group(1));
			if (QUESTION_FIGURE.equals(tokens[2])) {

				if (tokens.length > 3 && !tokens[3].isEmpty()) {

					if (0 != q.addSelection(new QuestionS(selectionNumber, tokens[3], true))) { throw new ParserException(
							ParserException.SYNTAX_ERROR, "[文法のエラー] 選択  'S(" + selectionNumber + ")' はありますした。 行 "
									+ parserState.getLineNumber()); }
				}

			} else {
				if (0 != q.addSelection(new QuestionS(selectionNumber, tokens[2], false))) { throw new ParserException(
						ParserException.SYNTAX_ERROR, "[文法のエラー] 選択  'S(" + selectionNumber + ")' はありますした。 行 "
								+ parserState.getLineNumber()); }
			}
		} else if ((matcher = QUESTION_WRITE.matcher(token1)).matches()) {
			int writeNumber = Integer.parseInt(matcher.group(1));
			try {
				if (0 != q.addWrite(new QuestionWR(writeNumber, Integer.parseInt(tokens[2])))) { throw new ParserException(
						ParserException.SYNTAX_ERROR, "[文法のエラー] 'WR(" + writeNumber + ")' はありますした。 行 "
								+ parserState.getLineNumber()); }
			} catch (NumberFormatException e) {
				CSVParser.parserState.setToken(tokens[2]);
				throw new ParserException(ParserException.SYNTAX_ERROR, SystemConfig.getConfigLanguage()
						.getParserMessage(Config.SYNTAX_ERROR));
			}
		} else if ((matcher = QUESTION_SCORE.matcher(token1)).matches()) {
			int scoreNumber = Integer.parseInt(matcher.group(1));
			if (QUESTION_EXAMINER_INSERT_TYPE_MARK.equals(tokens[2])) {// VINP
				//				QuestionSC qSC = new QuestionSC(scoreNumber, 0);
				//				question.addScore(qSC);
				QuestionAN vINP = new QuestionAN(0, Question.QUESTION_EXAMINER_INSERT_TYPE_MARK);//VINP
				q.addAnswer(vINP);
				vINP.setScoreInt(0);
			} else if ((matcher = QUESTION_ANSWER.matcher(tokens[2])).matches()) {
				int score = Integer.parseInt(tokens[3]);
				//					QuestionSC qSC = new QuestionSC(scoreNumber, score);
				//					question.addScore(qSC);
				QuestionAN answer = q.getAnswer(Integer.parseInt(matcher.group(1)));
				if (answer == null) { throw new ParserException(ParserException.SEMANTIC_ERROR, "[意味のエラー]　解答'"
						+ tokens[2] + "'　がありません、 行 " + parserState.getLineNumber()); }
				answer.setScoreInt(score);
			} else {
				CSVParser.parserState.setToken(tokens[2]);
				throw new ParserException(ParserException.SYNTAX_ERROR, SystemConfig.getConfigLanguage()
						.getParserMessage(

						Config.SYNTAX_ERROR));
			}

		} else if ((matcher = QUESTION_ANSWER.matcher(token1)).matches()) {
			int answerNumber = Integer.parseInt(matcher.group(1));
			boolean isTypeValid = false;
			isTypeValid = QUESTION_ANSWER_TYPE_KS.equals(tokens[2])
					|| QUESTION_ANSWER_TYPE_KSA.equals(tokens[2])
					|| QUESTION_ANSWER_TYPE_KSO.equals(tokens[2]) ||

					QUESTION_ANSWER_TYPE_KW.equals(tokens[2]) ||

					QUESTION_ANSWER_TYPE_KWA.equals(tokens[2])
					|| QUESTION_ANSWER_TYPE_KWAA.equals(tokens[2])
					|| QUESTION_ANSWER_TYPE_KWAO.equals(tokens[2]) ||

					QUESTION_ANSWER_TYPE_KWP.equals(tokens[2])
					|| QUESTION_ANSWER_TYPE_KWPA.equals(tokens[2])
					|| QUESTION_ANSWER_TYPE_KWPO.equals(tokens[2]);
			if (!isTypeValid) {
				CSVParser.parserState.setToken(tokens[2]);
				throw new ParserException(ParserException.SYNTAX_ERROR, SystemConfig.getConfigLanguage()
						.getParserMessage(Config.SYNTAX_ERROR));
			}
			QuestionAN answer = new QuestionAN(answerNumber, tokens[2]);
			if (TYPE_SELECTION.equals(q.getType())) {
				for (int i = 3; i < tokens.length; i++) {
					if (tokens[i].isEmpty()) continue;

					if ((matcher = QUESTION_SELECTION.matcher(tokens[i])).matches()) {
						QuestionS selection = q.getSelection(Integer.parseInt(matcher.group(1)));
						if (answer == null) { throw new ParserException(ParserException.SEMANTIC_ERROR,
								"[意味のエラー]  選択　'" + tokens[i] + "'　がありません、 行 " + parserState.getLineNumber()); }
						answer.addSelection(selection);
					} else {
						CSVParser.parserState.setToken(tokens[2]);
						throw new ParserException(ParserException.SYNTAX_ERROR, SystemConfig.getConfigLanguage()
								.getParserMessage(Config.SYNTAX_ERROR));
					}
				}
			} else {
				for (int i = 3; i < tokens.length; i++) {
					if (!tokens[i].isEmpty()) answer.addWrite(tokens[i]);
				}
			}
			q.addAnswer(answer);
		} else {
			CSVParser.parserState.setToken(token1);
			throw new ParserException(ParserException.SYNTAX_ERROR, SystemConfig.getConfigLanguage().getParserMessage(
					Config.SYNTAX_ERROR));
		}
		//		System.out.println("NO " + q.getQuestionNo() + ": " + q.getContent());
	}

	private static void parseHeader(Header h, String line) throws ParserException {
		String[] tokens = line.split(",");
		for (int i = 0; i < tokens.length; i++) {
			tokens[i] = tokens[i].trim();
		}

		String keyword = tokens[0];
		if (TITLE.equals(keyword)) { //Title
			h.setTitle(tokens[1]);
		} else if (SUBTITLE.equals(keyword)) { //Subtitle
			h.setSubtitle(tokens[1]);
		} else if (START_DATE_TIME.equals(keyword)) { //StartDate
			h.setStartTime(getTime(tokens[1], tokens[2], tokens[3], tokens[4], tokens[5], tokens[6]));
		} else if (END_DATE_TIME.equals(keyword)) { //EndDate
			h.setEndTime(getTime(tokens[1], tokens[2], tokens[3], tokens[4], tokens[5], tokens[6]));
		} else if (TEST_DURATION.equals(keyword)) { //Test Duration
			h.setEndTime(h.getStartTime() + getTime(tokens[1], tokens[2]));
		} else if (EXAMINEE.equals(keyword)) { //Examinee
			h.getExamineeList().add(getExaminee(tokens[1], tokens[2], tokens[3], tokens[4], tokens[5], tokens[6]));
		} else if (TEST_TYPE.equals(keyword)) {
			if (Test.TYPE_FIX.equals(tokens[1]) || Test.TYPE_UNFIX.equals(tokens[1])) {
				h.setType(tokens[1]);
			} else {
				CSVParser.parserState.setToken(tokens[1]);
				throw new ParserException(ParserException.SYNTAX_ERROR, SystemConfig.getConfigLanguage()
						.getParserMessage(Config.SYNTAX_ERROR));
			}
		} else {
			CSVParser.parserState.setToken(keyword);
			throw new ParserException(ParserException.SYNTAX_ERROR, SystemConfig.getConfigLanguage().getParserMessage(
					Config.SYNTAX_ERROR));
		}

	}

	private static Examinee getExaminee(String name, String nameV, String id, String idV, String pw, String pwV)
			throws ParserException {
		if (!EXAMINEE_NAME.equals(name)) {
			CSVParser.parserState.setToken(name);
		} else if (!EXAMINEE_ID.equals(id)) {
			CSVParser.parserState.setToken(id);
		} else if (!EXAMINEE_PASSWORD.equals(pw)) {
			CSVParser.parserState.setToken(pw);
		} else {
			Examinee examinee = new Examinee();
			examinee.setName(nameV);
			examinee.setUsername(idV);
			examinee.setPassword(pwV);
			return examinee;
		}
		throw new ParserException(ParserException.SYNTAX_ERROR, SystemConfig.getConfigLanguage().getParserMessage(
				Config.SYNTAX_ERROR));
	}

	public static long getTime(String durationS, String unitS) throws ParserException {

		int amount = Integer.valueOf(durationS);
		if (amount < 0) {
			CSVParser.parserState.setToken(durationS);
			throw new ParserException(ParserException.SYNTAX_ERROR, SystemConfig.getConfigLanguage().getParserMessage(
					Config.SEMANTIC_ERROR));
		}

		unitS = unitS.trim();
		if ("h".equals(unitS)) {
			return toMillis(HOUR, amount);
		} else if ("m".equals(unitS)) {
			return toMillis(MINUTE, amount);
		} else if ("s".equals(unitS)) {
			return toMillis(SECOND, amount);
		} else {
			CSVParser.parserState.setToken(unitS);
			throw new ParserException(ParserException.SYNTAX_ERROR, SystemConfig.getConfigLanguage().getParserMessage(
					Config.SYNTAX_ERROR));
		}
	}

	private static long toMillis(int unit, long amount) {
		switch (unit) {
			case DAY:
				return amount * 86400000; //milliseconds in 1 day
			case HOUR:
				return amount * 3600000; //milliseconds in 1 hour
			case MINUTE:
				return amount * 60000; //milliseconds in 1 minute
			case SECOND:
				return amount * 1000; //milliseconds in 1 second
			default:
				throw new IllegalArgumentException();
		}
	}

	@SuppressWarnings("deprecation")
	private static long getTime(String yearS, String monthS, String dayS, String hourS, String minuteS, String secondS)
			throws ParserException {
		try {
			int year = Integer.valueOf(yearS);
			int month = Integer.valueOf(monthS);
			int day = Integer.valueOf(dayS);
			int hour = Integer.valueOf(hourS);
			int minute = Integer.valueOf(minuteS);
			int second = Integer.valueOf(secondS);
			Date date = new Date(year - 1900, month - 1, day, hour, minute, second);
			return date.getTime();
		} catch (NumberFormatException e) {
			throw new ParserException(ParserException.SYNTAX_ERROR, SystemConfig.getConfigLanguage().getParserMessage(
					Config.SYNTAX_ERROR));
		}
	}

	@Deprecated
	public static Test parseCSV(String filename) throws ParserException, IOException {
		return parseCSV(new FileInputStream(filename));
	}

	private static String removeComment(String content) {
		String afterRemoveComment = content;
		Matcher matcher = REMOVE_COMMENT_MULTIPLE_LINE_2.matcher(content);
		while (matcher.find()) {
			String comment = matcher.group(0);
			int numberLineSeparator = 0;
			for (int i = 0; i < comment.length(); i++) {
				if ('\n' == comment.charAt(i)) numberLineSeparator++;
			}
			StringBuilder replacedString = new StringBuilder("");
			for (int i = 0; i < numberLineSeparator; i++) {
				replacedString.append(System.getProperty("line.separator"));
			}
			afterRemoveComment = afterRemoveComment.replace(comment, replacedString.toString());
		}

		afterRemoveComment = REMOVE_COMMENT_SINGLE_LINE_2.matcher(afterRemoveComment).replaceAll("");

//		System.out.println(afterRemoveComment);
		return afterRemoveComment;
	}
}
